# -*- coding: utf-8 -*-
"""
Created on Fri Jan  1 19:44:45 2021
用于计算评价指标的一个类
@author: 59567
"""
from sklearn.metrics import mean_absolute_error as mae
from sklearn.metrics import mean_squared_error as mse
import numpy as np


class Metrics():

    def __init__(self, targets, predictions, y_train_targets, y_valid_targets):
        super(Metrics, self).__init__()
        self.targets = targets
        self.predictions = predictions
        self.ytt = y_train_targets
        self.yvt = y_valid_targets
        self.metrics = {'mae': self._mae, 'mse': self._mse, 'acc': self._acc,
                        'mae_y': self._mae_y, '1_mae_y': self._1_mae_y,
                        'vbp': self._vbp, 'macc': self._macc, }
        self.metrics_result = self.get_metrics()
        self.ok = self._check()

    def _check(self):
        if self.targets.shape != self.predictions.shape:
            s = 'targets shape:%s is not equal predictions shape%s' % \
                (self.targets.shape, self.predictions.shape)
            raise ValueError(s)

    def _mae(self, y, y_p):
        if len(y) == 1:
            mae_result = abs(y - y_p)
        else:
            mae_result = mae(y, y_p)
        return mae_result

    def _mae_y(self, y, y_p):
        return self._mae(y, y_p) / abs(np.mean(y))

    def _1_mae_y(self, y, y_p):
        return abs(1 - self._mae_y(y, y_p))

    def _mse(self, y, y_p):
        if len(y) == 1:
            mse_result = (y - y_p) * (y - y_p)
        else:
            mse_result = mse(y, y_p)
        return mse_result

    def _acc(self, y, y_p):
        if len(y) == 1:
            raise ValueError('y长度必须大于1否则无法计算方向准确率')
        else:
            re = []
            for i in range(1, len(y)):
                t, t_ = y[i], y[i - 1]
                p = y_p[i]
                if (t - t_) * (p - t_) >= 0:
                    re.append(1)
                else:
                    re.append(0)
        return np.mean(re)

    def _vbp(self, y, y_p, ytt, yvt):  # voltality_based_performance
        y_tv = np.concatenate((ytt, yvt))
        y_tv_diff = np.abs(np.diff(y_tv))
        y_tv_diffsort = sorted(np.unique(y_tv_diff))
        if len(y_tv_diffsort) >= 3:
            y_tv_max = 0.5 * y_tv_diffsort[-1] + 0.3 * y_tv_diffsort[-2] + 0.2 * y_tv_diffsort[-3]
        elif len(y_tv_diffsort) >= 2:
            y_tv_max = 0.6 * y_tv_diffsort[-1] + 0.4 * y_tv_diffsort[-2]
        else:
            y_tv_max = y_tv_diffsort[-1]
        mae = self._mae(y, y_p)
        return max(0, 1 - mae / (1.5 * y_tv_max))

    def _macc(self, y, y_p, ytt, yvt):
        y_tv = np.concatenate((ytt, yvt))
        y_tv_diff = np.abs(np.diff(y_tv))
        y_tv_diffsort = sorted(np.unique(y_tv_diff))
        if len(y_tv_diffsort) >= 3:
            y_tv_max = 0.5 * y_tv_diffsort[-1] + 0.3 * y_tv_diffsort[-2] + 0.2 * y_tv_diffsort[-3]
        elif len(y_tv_diffsort) >= 2:
            y_tv_max = 0.6 * y_tv_diffsort[-1] + 0.4 * y_tv_diffsort[-2]
        else:
            y_tv_max = y_tv_diffsort[-1]
        mae = self._mae(y, y_p)
        if len(y) == 1:
            raise ValueError('y长度必须大于1否则无法计算方向准确率')
        else:
            re = []
            lmda = 0.2
            for i in range(1, len(y)):
                t, t_ = y[i], y[i - 1]
                p = y_p[i]
                if (t - t_) * (p - t_) >= 0:
                    re.append(max(0, 1 - mae / (1.5 * y_tv_max)))
                else:
                    re.append(max(0, 1 - (1 + lmda) * mae / (1.5 * y_tv_max)))
        return np.mean(re)

    def get_metrics(self, metrics_result={}):
        for m, fun in self.metrics.items():
            m_m = []
            if m in ['vbp', 'macc']:
                for c in range(self.targets.shape[1]):
                    y = self.targets[:, c]
                    y_p = self.predictions[:, c]
                    ytt = self.ytt[:, c]
                    yvt = self.yvt[:, c]
                    if m == 'vbp':
                        m_m.append(self._vbp(y, y_p, ytt, yvt))
                    else:
                        m_m.append(self._macc(y, y_p, ytt, yvt))
            else:
                for c in range(self.targets.shape[1]):
                    y = self.targets[:, c]
                    y_p = self.predictions[:, c]
                    m_m.append(fun(y, y_p))

            metrics_result[m] = np.array(m_m)
        return metrics_result


if __name__ == "__main__":
    y_t = np.array([[1, 3, 4, 5], [8, 5, 7, 4]])
    y_p = np.array([[3, 4, 6, 6], [9, 7, 5, 3]])
    metrics = Metrics(y_t, y_p)
    metrics_result = metrics.metrics_result
